#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<sys/epoll.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<dirent.h>
#include<ctype.h>

#include"epllo_server.h"

#define MAXSIZE 2000

void epllo_run(int port) 
{
	//创建一个epllo树的根节点
	int epfd = epoll_create(MAXSIZE);
	if (epfd == 1) {
		perror("epllo_create error");
		exit(1);
	}
	//添加要监听的节点
	//添加监听的lfd
	int lfd = init_listen_fd(port, epfd);
	//委托内核检测添加到树节点上
	struct epoll_event all[MAXSIZE];
	
	while (true)
	{
		int ret=epoll_wait(epfd, all, MAXSIZE, -1);
		if (ret == -1) 
		{
			perror("epllo_wait error");
			exit(1);
		}
		//遍历发生变化的节点
		for (int i = 0; i < ret; i++) 
		{
			//只处理读事件，其他默认不处理
			struct epoll_event* pev = &all[i];
			if (!(pev->events& EPOLLIN))   //不是读事件
			{
				continue;
			}
			if (pev->data.fd == lfd)
			{
				//接收连接请求
				do_accpet(lfd,epfd);
			}
			else
			{
				//读数据
				do_read(pev->data.fd,epfd);
			}
		}
	}
}

int init_listen_fd(int port,int epfd)
{
	//创建监听套接字
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if (lfd == -1)
	{
		perror("socket error");
		exit(1);
	}

	struct sockaddr_in serv;
	memset(&serv, 0, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(port);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);

	//端口复用
	int flag = 1;
	setsockopt(lfd,SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
	int ret = bind(lfd, (struct sockaddr*) & serv, sizeof(serv));
	if (ret == -1)
	{
		perror("bind error");
		exit(1);
	}

	//设置监听
	ret = listen(lfd, 64);
	if (ret == -1)
	{
		perror("listen error");
		exit(1);
	}

	
	//lfd添加到epllo 树上
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = lfd;
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
	return lfd;
}

//接收http 连接请求
void do_accpet(int lfd,int epfd)
{
	struct sockaddr_in client;
	socklen_t len = sizeof(client);

	//返回用于通信的文件描述符
	int cfd=accept(lfd, (struct sockaddr*) & client, &len);
	if (cfd == 1) {
		perror("accept error");
		exit(1);
	}
	//打印客户端信息
	char ip[64] = { 0 };
	printf("New Client IP:%s,port:%d,cfd=%d\n",
		inet_ntop(AF_INET, &client.sin_addr.s_addr,ip, sizeof(ip)),
		ntohs(client.sin_port),cfd);

	//设置cfd为非阻塞
	int flag = fcntl(cfd, F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(cfd, F_SETFL, flag);

	//得到的新节点挂到epoll 树上
	struct epoll_event  ev;
	ev.data.fd = cfd;
	//边沿非阻塞模式
	ev.events = EPOLLIN | EPOLLET;
	int ret=epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
	if (ret == -1)
	{
		perror("epoll_ctl error");
		exit(1);
	}
}

//读数据
void do_read(int cfd,int epfd)
{
	//将读取的数据存到buf
	char line[1024] = { 0 };
	//请求行
	int len=get_line(cfd, line, sizeof(line));
	if (len == 0)
	{
		printf("客户端断开连接...\n");
		//关闭套接字 从epoll 树上del
		dis_connect(cfd, epfd);
		
	}
	else if (len == -1)
	{
		//关闭套接字 从epoll 树上del
		perror("recv error\n");
		exit(1);
	}
	else
	{
		printf("请求行数据:%s", line);
		//还有数据
		printf("============请求头==========\n");
		while (len>0)
		{
			char buffer[1024] = { 0 };
			len=get_line(cfd,buffer, sizeof(buffer));
			printf(buffer);
		}
	}

	//请求行  get /xxx http:1.1
	//判断是否为get 请求
	if (strncasecmp("get", line, 3) == 0)
	{
		//处理http请求
		http_request(line,cfd);

		//主动断开
		dis_connect(cfd, epfd);
	}

}
//处理http请求
/*
浏览器发来数据

*/
void http_request(const char* request,int cfd)
{
	//拆分http 请求行^…
	char method[12] = { 0 }, path[1024] = { 0 }, protocol[12] = { 0 };
	//正则表达式提取请求行
	sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
	printf("method=%s path=%s protocol=%s\n", method, path, protocol);

	//处理path /xxxx
	//转码  不能识别的中文转换为汉字  
	//如果浏览器发送过来的url 中有中文需要解码
	/*
	http://192.168.92.128/%e8%8a%ad%e8%904%e8%89%ab%98%e6%b8%85.kux
	浏览器不支持中文的url
	需要对编码进行转换
	*/
	decode_str(path,path);
	char* file = path + 1;
	//如果没有指定res dir
	if (strcmp(path, "/") == 0)
	{
		//资源目录当前位置
		file = "./";
	}
	//获取文件属性
	struct stat st;
	int ret = stat(file, &st);
	if (ret == -1)
	{
		//show 404
		send_respod_head(cfd, 404, "File Not Found", ".html", -1);
		send_file(cfd, "404.html");
		perror("stat error\n");
		//exit(1);
	}
	//判断是目录还是文件
	if (S_ISDIR(st.st_mode))  //目录
	{
		send_respod_head(cfd, 200, "OK", get_file_type(".html"), -1);
		//发送目录消息
		send_dir(cfd, file);
	}
	else if (S_ISREG(st.st_mode)) //文件
	{
		//发送消息报头
		send_respod_head(cfd, 200, "OK",get_file_type(file),st.st_size);
		send_file(cfd,file);
	}

}

void send_dir(int cfd, const char* dirname)
{
	//拼接一个html页面 <table>
	char buf[4096] = { 0 };
	char enstr[1024] = { 0 };

	sprintf(buf, "<html><head><title>目录名：%s</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/></head>",dirname);
	sprintf(buf + strlen(buf), "<body><h1>当前目录：%s</h1><table>", dirname);
	
	char path[1024] = { 0 };
	struct dirent** ptr;
	int num = scandir(dirname,&ptr,NULL, alphasort);
	//printf("sendir---------------\n");
	for (int i = 0; i < num; i++)
	{
		char* name = ptr[i]->d_name;
		//字符串拼接
		sprintf(path, "%s/%s", dirname, name);
		struct stat st;
		stat(path, &st);

		encode_str(enstr, sizeof(enstr), name);
		//如果是文件
		if(S_ISREG(st.st_mode))
		{
			sprintf(buf + strlen(buf), "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
				enstr, name, st.st_size);
		}
		else if (S_ISDIR(st.st_mode))
		{
			sprintf(buf + strlen(buf), "<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",
				enstr, name, st.st_size);
		}
		//发送数据
		send(cfd, buf, strlen(buf), 0);
		printf(buf);
		memset(buf, 0, sizeof(buf));
		memset(enstr, 0, sizeof(enstr));

	}
	sprintf(buf,"</table></body></html>");
	send(cfd, buf, strlen(buf), 0);
	printf(buf);
	printf("dir message send OK!!!\n");

#if 0
	DIR* dir = opendir(dirname);
	if (dir == NULL)
	{
		perror("opendir error\n");
		exit(1);
	}
	//读目录
	struct dirent* ptr = NULL;

	while ((ptr==readdir(dir))!=NULL)
	{
		sprintf(buf,"<tr><td></td>")
		char* name = ptr->d_name;
	}
	closedir(dir);
#endif
}
//发送消息报头
void send_respod_head(int cfd, int no,const char* desp,const char* type,long len)
{
	char buf[1024] = { 0 };
	//状态行
	sprintf(buf, "http/1.1 %d %s\r\n", no, desp);
	send(cfd, buf, strlen(buf),0);

	//消息报头
	sprintf(buf, "Content-Type:%s\r\n",type);
	send(cfd, buf, strlen(buf), 0);

	sprintf(buf, "Content-Length:%d\r\n",len);
	send(cfd, buf, strlen(buf), 0);

	//空行
	send(cfd, "\r\n", 2, 0);

}

void send_file(int cfd,const char* filename)
{
	//打开文件
	int fd = open(filename, O_RDONLY);
	if (fd == -1)
	{
		//show 404
		return;
	}
	//循环读取文件
	char buf[4096] = { 0 };
	int len = 0;
	while ((len=read(fd,buf,sizeof(buf)))>0)
	{
		//边读边发
		send(cfd, buf, len, 0);
	}

	//读取文件出错
	if (len == -1)
	{
		perror("read file error\n");
		exit(1);
	}
	close(fd);
	printf("dir file send OK!!!\n");
}
//返回读取个数
int get_line(int sock, char* buf, int size) 
{
	int count = 0;
	char ch = '\0';
	int len = 0;

	while ((count < size - 1) && ch != '\n') 
	{
		len = recv(sock, &ch,1,0);
		if (len>0)
		{
			if (ch == '\r')
			{
				len = recv(sock, &ch, 1, MSG_PEEK);
				if (len > 0 && ch == '\n')
				{
					recv(sock, &ch, 1, 0);
				}
				else
				{
					ch = '\n';
				}
			}
			buf[count] = ch;
			count++;
		}
		else
		{
			ch = '\n';
		}
	}
	buf[count] = '\0';
	if (len == -1)
	{
		count = -1;
	}
	//printf("line:%s\n", buf);
	return count;
}

void dis_connect(int cfd,int epfd)
{
	int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
	if (ret == -1)
	{
		perror("epoll_ctl del cfd error\n");
		exit(1);
	}
	close(cfd);
}

//编码
/*
	to 首地址  from 不可作为左值被修改

*/
void encode_str(char* to, int tosize, const char* from)
{
	int tolen = 0;
	for (tolen = 0;*from != '\0' && tolen + 4 < tosize; ++from)
	{
		///_.-~ 特殊字符不需要转换
		//isalnum确定整数是否表示字母数字字符
		/*
			判断是否需要编码 一般的数组字母 /_.-~不需要转
		*/
		
		if (isalnum(*from) || strchr("/_.-~",*from) != (char*)0)
		{
			*to = *from;
			++to;
			++tolen;
		}
		else   //需要转换该字符
		{
			//%23%04%            字符变成16进制数
			/*%e8%8a%ad*/
			sprintf(to,"%%%02x", (int)*from & 0xff);
			to += 3;
			tolen += 3;
		}

	}
	to = '\0';
}

//解码  值传递传递一级指针
void decode_str(char* to, char* from)
{
	for (;*from != '\0';++to,++from)
	{
		//% 十六进制数 %AF%1a%34%89->十进制
		if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
		{
			*to = hexit(from[1]) * 16 + hexit(from[2]);
			from += 2;
		}
		else
		{
			*to = *from;
		}
	}
	*to = '\0';
}
//16 进制转换为10进制
int hexit(char c)
{
	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'a' && c <= 'f')
	{
		return c - 'a' + 10;
	}
	if (c >= 'A' && c <= 'F')
	{
		return c - 'A' + 10;
	}
	return 0;
}

const char* get_file_type(const char* name)
{
	const char* dot;
	dot = strrchr(name, '.');
	if (dot == NULL)
		return "text/plain charset=utf8";
	if(strcmp(dot,".html")==0||strcmp(dot,".htm")==0)
		return "text/html charset=utf8";
	if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
		return "image/jpeg";
	if (strcmp(dot, ".gif") == 0)
		return "image/gif";
	if (strcmp(dot, ".png") == 0)
		return "image/png";
	if (strcmp(dot, ".css") == 0)
		return "text/css";
	if (strcmp(dot, ".au") == 0)
		return "audio/basic";
	if (strcmp(dot, ".wav") == 0)
		return "audio/wav";
	if (strcmp(dot, ".avi") == 0)
		return "video/x-msvideo";

	if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
		return "video/quicktime";
	if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
		return "video/mpeg";
	if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
		return "model/vrml";
	if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
		return "audio/midi";
	if (strcmp(dot, ".mp3") == 0)
		return "video/mpeg";
	if (strcmp(dot, ".ogg") == 0)
		return "application/ogg";
	if (strcmp(dot, ".pac") == 0)
		return "application/x-ns-proxy-autoconfig";

	return "text/plain charset=utf8";
}